//
//  MethodReceiver.m
//  TestThread
//
//  Created by Chris Burns on 10-07-08.
//  Copyright 2010 University of Calgary. All rights reserved.
//

#import "MethodReceiver.h"


@implementation MethodReceiver


@synthesize  operationQueue, methodResponders;

#pragma mark User Methods




/* initWithAddress: andPort - We initialize the socket, operationQueue and the timer but do not begin operation
- (id) initWithAddress:(NSString *)ipAddress andPort:(NSUInteger)port {
	
	if (self = [super init]) {
		
		socket = [[AsyncSocket alloc] init];
		operationQueue = [[NSOperationQueue alloc] init];
		methodResponders = [[NSMutableArray alloc] init]; 
		
		hostAddress = ipAddress; 
		hostPort = port; 
		
	}
	
	return self; 
	
}

*/

- (id) initWithPort:(NSUInteger)port {
	
	if (self = [super init]) {
		
		socket = [[AsyncSocket alloc] init];
		operationQueue = [[NSOperationQueue alloc] init];
		methodResponders = [[NSMutableArray alloc] init]; 
		
		
		NSString *startAddressFromConfig = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"iOSRC IP Address"];
		
		//Check to see if the IP Address has been properly configured
		if (startAddressFromConfig == nil) {
			
			
			NSException *exception = [NSException exceptionWithName:@"Configuration Error"
															reason:@"The iOSRC IP Address is likely no configured in the *-Info.plist file"
														   userInfo:nil];
			
			
			
			[exception raise];
			
			
		}
		
		
		
		
		hostAddress = [NSMutableString stringWithString:startAddressFromConfig]; 
		
		
	
		hostPort = port; 
		
	}
	
	return self; 
	
	
	
	
}


/* startCommunication - This method connects to the serbver and begins the timer*/

- (void) startReceiving{
	
	
	socket.delegate = self; 
	
	[socket connectToHost:hostAddress onPort:hostPort error:nil];
	
	timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerReadMethod:) userInfo:nil repeats:YES];
	
	
}





#pragma mark Utility Methods



/* parseMethodCallAndSchedule - This method is responsible for creating an NSInvocationOperation and then  */

- (void) parseMethodCallAndExecute:(NSString *)methodCall {
	

	//Create XML Document from the methodCall to begin the parsing process.
	CXMLDocument *methodRequestDocument = [[CXMLDocument alloc] initWithXMLString:methodCall options:0 error:nil];	


	//  Find the methodName in the XML document. This will be first and will only exist once.
	CXMLElement *methodNameXML = [[methodRequestDocument nodesForXPath:@"//methodName" error:nil] objectAtIndex:0];
	NSString *methodName = [methodNameXML stringValue];


	// Find the messageID which will be a GUID generated by the server. There will only exist one.
	CXMLElement *messageIDXML = [[methodRequestDocument nodesForXPath:@"//messageID" error:nil] objectAtIndex:0];
	NSString* messageID = [messageIDXML stringValue];



	// Since there can be many parameters we gather all of them together and cycle through each node.
	CXMLElement *methodParametersXMLAll = [[methodRequestDocument nodesForXPath:@"//methodParameters" error:nil] objectAtIndex:0];
	NSArray *methodParametersXML = [methodParametersXMLAll children];


	// We wish to handle arrays here 
	NSMutableArray *methodParameterValues = [[NSMutableArray alloc] init];


	for (CXMLNode *node in methodParametersXML) {
		
		
		//(TODO) Handle the parameter types (NSDouble, NSInteger, NSData etc here) ... 
		
		[methodParameterValues addObject:[node stringValue]];
		
	}

	
	// The last parameter of any valid remote call method is always the messageID. 
	[methodParameterValues addObject:messageID];


	NSLog(@" Received MethodCall for method <%@> with %i paramters", methodName, [methodParameterValues count]); 
	
	[self createInvocationForMethodAndSchedule:methodName andParameters:methodParameterValues];
	
	///Fix!!!!!
	// methodCall is the result of creating the invocation and then it is scheduled into the operationQueue so it will run concurrently.
	//NSInvocationOperation *methodInvocationOperation = [self createInvocationForMethod:methodName andParameters:methodParameterValues];
	
	
	
	//[operationQueue addOperation:methodInvocationOperation];
	

}

/* creatInvocationForMethod: andParameters: withTarget: - This method creates and NSInvocationOperation for each of the MethodCalls 
 * it loads the code in via a selector and then add's each of the arguments provided. No primitive types are allowed. Then the 
 * invocation is returned to be scheduled by the calling method. 
 
 
 
 */
- (void) createInvocationForMethodAndSchedule:(NSString *) methodName andParameters: (NSMutableArray *) params{
	
	
	//(TODO) Fix this so it conforms to the all in all call methodology.....
	SEL methodSelector = NSSelectorFromString(methodName);
	
	
	int respondingMethodsCount = 0; 
	
	//We iterate through all the method responders to find which will respond to our selector
	for (id potentialTarget in methodResponders) {
		
		if ([potentialTarget respondsToSelector:methodSelector]) {
			
			respondingMethodsCount++; 
			
			
			NSMethodSignature *methodSig = [potentialTarget methodSignatureForSelector:methodSelector];
			NSInvocation *methodInvocation = [NSInvocation invocationWithMethodSignature:methodSig];
			
			
			
			if (([methodSig numberOfArguments] -2) != [params count]) { //We subtract 2 since all CocoaTouch methods take self and _cmd
				
				NSException *numberOfArgsException = [NSException exceptionWithName:@"Number of Arguments In Method Call Exception"
																			 reason:@"The number of argument is the responding method call is different the number of parameters passed."
																		   userInfo:nil];
				
				
				NSLog(@" Args: %i and Params: %i", [methodSig numberOfArguments], [params count]); 
				[numberOfArgsException raise]; 
				
				
				
				
			}
		
			
			
			//As we add parameters to methodInvocation we must be aware that the first two parameters (hidden) that are passed in are self and -cmd
			//This code based on code found at http://excitabyte.wordpress.com/2009/07/07/spawning-threads-using-selectors-with-multiple-parameters/ */
			[methodInvocation setTarget:potentialTarget]; //Index at 0 
			[methodInvocation setSelector:methodSelector]; //Index at 1
			
			//Add parameters into the method invocation.
			for (int i = 0; i<[params count]; i++) {
				
				
				id value = [params objectAtIndex:i];
				
				[methodInvocation setArgument:&value atIndex:(i+2)]; //i+2 is added as an offset for the two hidden params, setArgument requires a pointer
				
				
				
			}
			
			
			
			/*(NOTE)There are memory problems created by this */
			
			[methodInvocation retainArguments]; 
			
			
			NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithInvocation:methodInvocation];
			
		
			
			[operationQueue addOperation:invocationOperation]; 
		
			
	
			
			
		}
		
		
		
	}
	
	
	
	if (respondingMethodsCount == 0) {
		
		
	
		NSException *exception = [NSException exceptionWithName:@"MethodCall Selector Error"
														 reason:@"No object can be found which responds to the method name chosen. This could be because you have not added any responders or the method name is malformed"
													   userInfo:nil];
		
		
		[exception raise]; 
		
		NSLog(@"MethodCall Selector Error had method name <%@> and the current method responders are %@", methodName, methodResponders); 
								  
								
		
	}
	

	

	
	

	

	
	
	
}



/* timerReadMethod - This is the method called on interval by the timer. It simple calls a new "read" method on the socket which will not continously 
 * check for updates without it. */

- (void) timerReadMethod: (NSTimer *) theTimer {
	
	[socket readDataWithTimeout:1 tag:0];

}




/* dealloc - We send release messages to all class variables and send a dealloc message to the super class*/
- (void) dealloc {
	
	[socket release];
	[timer release];
	[operationQueue release];
	[methodResponders release]; 
	
	
	[super dealloc];
	
	
	
}











#pragma mark AsycSocket Delegate/Utility Methods

/* onSocketWillConnect: - This method (not a delegate) simply return TRUE to indicate we intend to connect to the socket */
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock 
{
	return TRUE;
	
}


/* onSocket: didConnectToHost: port: - This delegate method is called whenever the socket succeeds in connecting to the host */
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
	
	
	NSLog(@"Did connect to host with port %i on %@", port, host); 
	isConnectedToHost = YES;

}


/* onSocket: willDisconnectWithError: - This delegate method is called whenever the socket disconnects. */
-(void) onSocket:(AsyncSocket *) sock willDisconnectWithError:(NSError *) error {
	
	
	isConnectedToHost = NO;
	

}


/* onSocket: didReadData: withTag: - This delegate method is called whenever the socket successfully reads some data from the socket 
 * it is important to know what this data is not guaranteed (and is rarely likely) to contain a single MethodCall message. Thus it must be parsed 
 * based on this assumption. */

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
	
	NSString *fullString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
	NSArray *messages = [fullString componentsSeparatedByString:@"\n"];	//Newlines are what seperates the messages.

	
	for (NSString * s in messages) {
		
		if ([s length] == 0) { break;} //The last message in the list is an "" empty character.
		
		
		
		[self parseMethodCallAndExecute:s];
		
		
		
		
		

	}
	
}


@end
